/*
 * MicroEmulator 
 * Copyright (C) 2001 Bartek Teodorczyk <barteo@barteo.net>
 * 
 *  It is licensed under the following two licenses as alternatives:
 *    1. GNU Lesser General Public License (the "LGPL") version 2.1 or any newer version
 *    2. Apache License (the "AL") Version 2.0
 *
 *  You may not use this file except in compliance with at least one of
 *  the above two licenses.
 *
 *  You may obtain a copy of the LGPL at
 *      http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
 *
 *  You may obtain a copy of the AL at
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the LGPL or the AL for the specific language governing permissions and
 *  limitations.
 * 
 * Contributor(s): 
 *   3GLab
 *   Robert Helmer
 */

package javax.microedition.lcdui;

import org.microemu.device.DeviceFactory;
import org.microemu.device.InputMethod;
import org.microemu.device.InputMethodEvent;
import org.microemu.device.InputMethodListener;

public class TextField extends Item 
{
	/* 
	 * BlueWhaleSystems fix: Ramesh Nair - 16 May 2008
	 */
    private static final int TEXT_ORIGIN_X = 3;
    private static final int TEXT_ORIGIN_Y = 3;
    private static final int HEIGHT_ADDER = 8;
    
	public static final int ANY = 0;
	public static final int EMAILADDR = 1;
	public static final int NUMERIC = 2;
	public static final int PHONENUMBER = 3;
	public static final int URL = 4;	
	public static final int DECIMAL = 5;

	public static final int PASSWORD = 0x10000;
	public static final int UNEDITABLE = 0x20000;
	public static final int SENSITIVE = 0x40000;
	public static final int NON_PREDICTIVE = 0x80000;
	public static final int INITIAL_CAPS_WORD = 0x100000;
	public static final int INITIAL_CAPS_SENTENCE = 0x200000;
	
	public static final int CONSTRAINT_MASK = 0xffff;

	/*
	 * BlueWhaleSystems fix: Michael Busheikin - June 8, 2009
	 * 
	 * When SetConstraints is called, make sure setString is called so if the password mode of the 
	 * TextField changes the text is *'d or un*'d.
	 *  
	 * See ticket:3279 (SetupUserSettings: LWUIT: Native password field shows plaintext password)
	 */
	// New-ing the stringComponent here means it will be initialized when the constructor runs
	StringComponent stringComponent = new StringComponent();
	
	private String field;
	private volatile int caret;
	volatile int caretOldPosition = -1;
	private boolean caretVisible;
	private volatile boolean caretBlankIsInOffState = false;
	private int maxSize;
	private int constraints;
	
	private volatile boolean allowNewLineCharactersToBeEntered = false;
	

    /**
     * BlueWhaleSystems fix: Ramesh Nair - 15 May 2008
     * Refactored common listener code into {@link TextFieldInputMethodListenerBase}.
     */
	private InputMethodListener inputMethodListener = new TextFieldInputMethodListenerBase() 
	{
        protected void doRepaint()
        {
            TextField.this.repaint();
        }

        public boolean dontAllowNewLineChar()
        {
            return !TextField.this.allowNewLineCharactersToBeEntered;
        }

        public int getCaretPosition()
        {
            return TextField.this.getCaretPosition();
        }

        public int getConstraints()
        {
            return TextField.this.getConstraints();
        }

        protected int getOldCaretPosition()
        {
            return TextField.this.getOldCaretPosition();
        }

        protected Screen owner()
        {
            return TextField.this.owner;
        }

        protected void setCaretPosition( int newCaretPosition )
        {
            TextField.this.setCaretPosition( newCaretPosition );
        }

        protected void setCaretVisible( boolean visible )
        {
            TextField.this.setCaretVisible( visible );
        }

        protected void setString( String text, int caret, boolean showCaret )
        {
            TextField.this.setString( text, caret, showCaret );            
        }

        protected StringComponent sc()
        {
            return TextField.this.stringComponent;
        }

        protected void doUpdateHighlightedTextMarker( boolean shiftKeyIsDown )
        {
            TextField.this.showOrHideHighlightedTextMarker( shiftKeyIsDown );
        }
	};

	
	public TextField(String label, String text, int maxSize, int constraints) 
	{
		super(label);
		if (maxSize <= 0) {
			throw new IllegalArgumentException();
		}
		setConstraints(constraints);
        if (!InputMethod.validate(text, constraints)) {
            throw new IllegalArgumentException();
        }
		this.maxSize = maxSize;
		if (text != null) {
			setString(text);
		} else {
			setString("");
		}
		stringComponent.setWidthDecreaser(8);
		
        /**
         * BlueWhaleSystems fix: Ramesh Nair - 14 May 2008
         *
         * If the text content contains new line characters then allow 
         * new line characters to be entered.
         */
		allowNewLineCharactersToBeEntered = stringComponent.hasBreaks();
	}

	
	public String getString() 
	{
		return field;
	}

	
	public void setString(String text) 
	{
       /**
        * BlueWhaleSystems fix: Ramesh Nair - 30 Oct 2007
        * 
        * Implemented patch from:
        * http://sourceforge.net/tracker/index.php?func=detail&aid=1822083&group_id=30014&atid=397811
        * 
        * NullPointerException was being thrown when null was being passed to 
        * setString() method.                    
        */  	

        setString(text, (null==text ? 0 : text.length()), false);
	}
    
    
    /**
     * BlueWhaleSystems fix: Ramesh Nair - 26 Dec 2007
     * 
     * This method is called when we want to preset the text content of this 
     * textfield and also when we're typing characters into the text field. 
     * We've added a 'showCaret' parameter so that we can specify that the 
     * caret be visible when we're entering text into the text field.
     */      
    void setString(String text, int caret, boolean showCaret)
    {
        if (!InputMethod.validate(text, constraints)) {
            throw new IllegalArgumentException();
        }
        if (text == null) {
            field = "";
            stringComponent.setText("");
        } else {
            if (text.length() > maxSize) {
                throw new IllegalArgumentException();
            }
            field = text;
            if ((constraints & PASSWORD) == 0) {
                stringComponent.setText(text);
            } else {
                StringBuffer sb = new StringBuffer();
                for (int i = 0; i < text.length(); i++) {
                    sb.append('*');
                }
                stringComponent.setText(sb.toString());
            }
        }
        setCaretPosition(caret);
        setCaretVisible(showCaret);
        repaint();
    }

	
	public int getChars(char[] data) 
	{
		if (data.length < field.length()) {
			throw new ArrayIndexOutOfBoundsException();
		}
		getString().getChars(0, field.length(), data, 0);

		return field.length();
	}

	
	public void setChars(char[] data, int offset, int length) 
	{
		if (data == null) {
			setString("");
		} else {
			if (length > maxSize) {
				throw new IllegalArgumentException();
			}
			String newtext = new String(data, offset, length);
			if (!InputMethod.validate(newtext, constraints)) {
                throw new IllegalArgumentException();
            }
			setString(newtext);
		}
		repaint();
	}

	
	public void insert(String src, int position) 
	{
		if (!InputMethod.validate(src, constraints)) {
            throw new IllegalArgumentException();
        }
		if (field.length() + src.length() > maxSize) {
			throw new IllegalArgumentException();
		}
		String newtext = "";
		if (position > 0) {
			newtext = getString().substring(0, position);
		}
		newtext += src;
		if (position < field.length()) {
			newtext += getString().substring(position + 1);
		}
		setString(newtext);
		repaint();
	}

	
	public void insert(char[] data, int offset, int length, int position) 
	{
		if (offset + length > data.length) {
			throw new ArrayIndexOutOfBoundsException();
		}
		insert(new String(data, offset, length), position);
	}

	
	public void delete(int offset, int length) 
	{
		if (offset + length > field.length()) {
			throw new StringIndexOutOfBoundsException();
		}
		String newtext = "";
		if (offset > 0) {
			newtext = getString().substring(0, offset);
		}
		if (offset + length < field.length()) {
			newtext += getString().substring(offset + length);
		}
		setString(newtext);
		repaint();
	}

	
	public int getMaxSize() 
	{
		return maxSize;
	}

	
	public int setMaxSize(int maxSize) 
	{
		if (maxSize <= 0) {
			throw new IllegalArgumentException();
		}
		if (field.length() > maxSize) {
			setString(getString().substring(0, maxSize));
		}
		this.maxSize = maxSize;
		return maxSize;
	}

	
	public int size() 
	{
		return field.length();
	}

	
	public synchronized int getCaretPosition() 
	{
		return caret;
	}

	/* 
	 * BlueWhaleSystems fix: Ramesh Nair - May 13, 2008
	 * Work towards text highlighting.
	 */
    synchronized int getOldCaretPosition() 
    {
        return caretOldPosition;
    }
	
	public void setConstraints(int constraints) 
	{
		if ((constraints & TextField.CONSTRAINT_MASK) < ANY
			|| (constraints & TextField.CONSTRAINT_MASK) > DECIMAL) {
			throw new IllegalArgumentException("constraints " + constraints + " is an illegal value");
		}
		this.constraints = constraints;
        if (!InputMethod.validate(field, constraints)) {
            setString("");
        } else {
            /*
             * BlueWhaleSystems fix: Michael Busheikin - June 8, 2009
             * 
             * When SetConstraints is called, make sure setString is called so the
             * string updates how it is displayed.
             * 
             * See ticket:3279 (SetupUserSettings: LWUIT: Native password field shows plaintext password)
             */
            setString( field );
        }
	}

	
	public int getConstraints() 
	{
		return constraints;
	}
	
	
	public void setInitialInputMode(String characterSubset)
	{
		// TODO implement
	}

	
	boolean isFocusable() 
	{
		return true;
	}

	
	int getHeight() 
	{
		return super.getHeight() + stringComponent.getHeight() + HEIGHT_ADDER;
	}

	
	int paint(Graphics g) 
	{
		super.paintContent(g);

		g.translate(0, super.getHeight());
		int savedColor = g.getColor();
		if (!hasFocus()) {
		    g.setGrayScale(127);
		}
		g.drawRect(
		        1, 1, 
				owner.getWidth() - 3, stringComponent.getHeight() + 4);
		if (!hasFocus()) {
		    g.setColor(savedColor);
		}
		g.translate(TEXT_ORIGIN_X, TEXT_ORIGIN_Y);
		paintContent(g);
        g.translate(-TEXT_ORIGIN_X, -TEXT_ORIGIN_Y);
		
		g.translate(0, -super.getHeight());

		return getHeight();
	}

	
	void paintContent(Graphics g) 
	{
		stringComponent.paint(g);
		
		if (caretVisible && !caretBlankIsInOffState) {
			int x_pos = stringComponent.getCharPositionX(caret);
			int y_pos = stringComponent.getCharPositionY(caret);
			g.drawLine(x_pos, y_pos, x_pos, y_pos + Font.getDefaultFont().getHeight());
		}
	}

    /**
     * BlueWhaleSystems fix: Ramesh Nair - 29 Jan 2008, 1 May 2008
     * 
     * This method is automatically called at regular time intervals from  
     * {@link Form#setTextCursorBlinkState(boolean)}.
     */     	
    protected void setTextCursorBlinkState(boolean aState)
    {
        caretBlankIsInOffState = !aState;        
    }


	synchronized void setCaretPosition(int position) 
	{
	    caretOldPosition = caret;
	    
       /**
        * BlueWhaleSystems fix: Ramesh Nair - 22 Jan 2007, 13 May 2008
        *
        * If a subclass overrides the #getString() and #setString() methods 
        * then depending on what funky stuff it does to the field text, the 
        * caret position might become invalid. 
        * We need to ensure that the caret position is always valid (i.e. 
        * within the limits imposed by the size of the current text). 
        */  	    
	    
        int scTxtLength = stringComponent.getText().length();
        
        if (scTxtLength < position)
        {
            position = scTxtLength;
        }	    

        if (0 > position)
        {
            position = 0;
        }

        caret = position;
	}

	
	void setCaretVisible(boolean state) 
	{
		caretVisible = state;
        /**
         * BlueWhaleSystems fix: Ramesh Nair - 29 Jan 2008
         * Caret shouldn't be blinking when user is moving it.
         */          
         caretBlankIsInOffState = false;		
	}

	
	int traverse(int gameKeyCode, int top, int bottom, boolean action) 
	{
		if (gameKeyCode == Canvas.UP) {
			if (top > 0) {
				return -top;
			} else {
				return Item.OUTOFITEM;
			}
		}
		if (gameKeyCode == Canvas.DOWN) {
			if (getHeight() > bottom) {
				return getHeight() - bottom;
			} else {
				return Item.OUTOFITEM;
			}
		}

		return 0;
	}

	
	void setFocus(boolean hasFocus) 
	{
		super.setFocus(hasFocus);
		if (hasFocus) {
			// register input listener
			InputMethod inputMethod = DeviceFactory.getDevice().getInputMethod();
			inputMethod.setInputMethodListener(inputMethodListener);
			inputMethod.setMaxSize(getMaxSize());
			setCaretVisible(true);
		} else {
			// unregister input listener
			DeviceFactory.getDevice().getInputMethod().removeInputMethodListener(inputMethodListener);
			setCaretVisible(false);
			
		   /**
		    * BlueWhaleSystems fix: Ramesh Nair - 14 May 2008
		    */			
			stringComponent.stopHighlightingText();
		}
	}	
	
	/**
    * BlueWhaleSystems fix: Ramesh Nair - 16 May 2008
    * 
    * Enable text selection using mouse.
    */
    void showOrHideHighlightedTextMarker(boolean shiftKeyIsDown)
    {
        if (shiftKeyIsDown)
        {
            int currentCaretPosition = getCaretPosition();
            int oldCaretPosition = getOldCaretPosition();
            
            if (currentCaretPosition != oldCaretPosition)
            {
                if (!stringComponent.weAreCurrentlyHighlightingText())
                {
                    stringComponent.markHighlightedTextStartPoint( oldCaretPosition );
                }

                stringComponent.markHighlightedTextEndPoint( currentCaretPosition );
            }
        }
        else
        {
            stringComponent.stopHighlightingText();
        }
    }	
	

   /**
    * BlueWhaleSystems fix: Ramesh Nair - 30 Oct 2007
    * 
    * Implemented patch from:
    * http://sourceforge.net/tracker/index.php?func=detail&aid=1813339&group_id=30014&atid=397811    
    */  
	void setOwner(Screen owner)
    {
        super.setOwner(owner);
        if (owner == null) setFocus(false);
    }
	
	
   /**
    * BlueWhaleSystems fix: Ramesh Nair - 16 May 2008
    */
    protected boolean pointerPressed( int x, int y )
    {
        updateCaretFromPointerCoordinates(x, y);
        showOrHideHighlightedTextMarker( false );
        return false;
    }


    
    
    /**
     * BlueWhaleSystems fix: Ramesh Nair - 16 May 2008
     */
    protected boolean pointerDragged( int x, int y )
    {
        updateCaretFromPointerCoordinates(x, y);
        showOrHideHighlightedTextMarker( true );
        return false;
    }    
	

    
    /**
     * BlueWhaleSystems fix: Ramesh Nair - 16 May 2008
     */
    private void updateCaretFromPointerCoordinates(int x, int y)
    {
        y -= (super.getHeight() + TEXT_ORIGIN_Y);
        x -= (TEXT_ORIGIN_X);
        setCaretPosition( stringComponent.getCaretPositionForXY( x, y ) );
    }

}
